/**
 * @file dxChannel.js
 * @module dxChannel
 * @description
 * Data communication channel module based on the native dxChannel C library.
 * This module provides a direct, handle-based interface for various communication channels,
 * including Serial Port (UART), USB (HID, Keyboard Wedge), and Wiegand.
 *
 * It is designed for high-performance, multi-threaded environments, allowing different
 * channels to be operated in parallel safely.
 *
 * @usage
 * // 1. Import the module
 * import channel from 'dxChannel.js';
 *
 * // 2. Open a channel (e.g., a serial port) and get its handle
 * let uart_handle = null;
 * try {
 *   uart_handle = channel.open(channel.TYPE.UART, "/dev/ttyS2");
 * } catch (e) {
 *   console.error("Failed to open UART:", e);
 * }
 *
 * if (uart_handle !== null) {
 *   try {
 *     // 3. Configure the channel
 *     channel.setUartParam(uart_handle, 9600, 8, 'N', 1);
 *
 *     // 4. Send data
 *     const dataToSend = new Uint8Array([0x01, 0x02, 0x03, 0x04]).buffer;
 *     const success = channel.send(uart_handle, dataToSend);
 *     dxLogger.info("Send successful:", success);
 *
 *     // 5. Receive data (this is a blocking call)
 *     const receivedData = channel.receive(uart_handle, 4, 1000); // Wait for 4 bytes for up to 1000ms
 *     if (receivedData) {
 *       dxLogger.info("Received data:", receivedData);
 *     }
 *   } catch (e) {
 *     console.error("Channel operation failed:", e);
 *   } finally {
 *     // 6. Close the handle when done
 *     channel.close(uart_handle);
 *   }
 * }
 */
import { channel as nativeChannel } from './libvbar-m-dxchannel.so'

const channel = {}

/**
 * @readonly
 * @enum {number}
 * Channel type enumeration.
 */
channel.TYPE = Object.freeze({
	USBKBW: 1,  // USB Keyboard Wedge
	USBHID: 2,  // USB Human Interface Device
	UART: 3,    // Serial Port (UART)
	WIEGAND: 4  // Wiegand interface
});

/**
 * @readonly
 * @enum {number}
 * IO Control command enumeration for `channel.ioctl`.
 */
channel.IOC_SET_CMD = Object.freeze({
	/** Set KBW channel configuration parameters */
	CHANNEL_IOC_SET_KBW_CONFIG: 1,
	/** Set KBW channel upper computer parameters */
	CHANNEL_IOC_SET_KBW_UPPER: 2,
	/** Set KBW online time */
	CHANNEL_IOC_SET_KBW_UPTIME: 3,
	/** Set KBW offline time */
	CHANNEL_IOC_SET_KBW_DOWNTIME: 4,
	/** Set HID channel report length */
	CHANNEL_IOC_SET_HID_REPORT_LEN: 5,
	/** Set UART channel parameters */
	CHANNEL_IOC_SET_UART_PARAM: 6,
	/** Set Wiegand channel working mode */
	CHANNEL_IOC_SET_WIEGAND_MODE: 7,
	/** Set Wiegand channel GPIO configuration */
	CHANNEL_IOC_SET_WIEGAND_GPIO: 8,
	/** Set Wiegand channel delay time */
	CHANNEL_IOC_SET_WIEGAND_DELAY: 9,
	/** Set Wiegand channel logging function */
	CHANNEL_IOC_SET_WIEGAND_LOG: 10
});

/**
 * @readonly
 * @enum {number}
 * Wiegand channel working modes.
 */
channel.WIEGAND_MODE = Object.freeze({
	/** Initial value for Wiegand mode */
	WIEGAND_MODE_INIT: 0,
	/** Wiegand 26-bit mode */
	WIEGAND_MODE_26: 1,
	/** Wiegand 34-bit mode */
	WIEGAND_MODE_34: 2,
	/** Wiegand 128-bit mode */
	WIEGAND_MODE_128: 3,
	/** Wiegand 256-bit mode */
	WIEGAND_MODE_256: 4,
	/** Wiegand 2048-bit mode */
	WIEGAND_MODE_2048: 5,
	/** Custom Wiegand mode, max 6400 bits */
	WIEGAND_MODE_CUSTOM: 6
});

/**
 * Opens a communication channel and returns its handle.
 * @param {number} type - The channel type, from `channel.TYPE` enum.
 * @param {string} path - The channel device path, e.g., "/dev/ttyS2". Note: The path may vary on different hardware. Please refer to `dxDriver.js` for device-specific constants.
 * @returns {number} The numeric handle for the opened channel, used in all subsequent operations.
 * @throws {Error} If the channel fails to open or if parameters are invalid.
 */
channel.open = function (type, path) {
	if (type === undefined || type === null) {
		throw new Error("channel.open: 'type' parameter is required.")
	}
	// Validate that the provided type is a valid value from the enum
	if (!Object.values(channel.TYPE).includes(type)) {
		throw new Error(`channel.open: invalid 'type' parameter. Please use a value from channel.TYPE.`);
	}
	if (path === undefined || path === null) {
		throw new Error("channel.open: 'path' parameter is required.")
	}

	try {
		const handle_id = nativeChannel.open(type, path);
		return handle_id;
	} catch (e) {
		// The C layer throws an exception on failure. We catch it and re-throw a more user-friendly JS error.
		throw new Error(`channel.open: failed to open channel with type ${type} and path '${path}'. Reason: ${e.message}`);
	}
}

/**
 * Sends data to a specified channel.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @param {ArrayBuffer} buffer - The binary data to send.
 * @returns {number} The number of bytes successfully sent, or a negative value on failure.
 *                   Note: A successful return only indicates data has been sent from the local buffer,
 *                   not that it has been received by the remote end.
 */
channel.send = function (handle_id, buffer) {
	if (handle_id === undefined || handle_id === null) {
		throw new Error("channel.send: 'handle_id' parameter is required.")
	}
	if (buffer === undefined || buffer === null) {
		throw new Error("channel.send: 'buffer' parameter is required.")
	}
	return nativeChannel.send(handle_id, buffer);
}

/**
 * Receives data from a specified channel. This is a blocking call.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @param {number} size - The number of bytes to receive.
 * @param {number} [timeout=10] - The maximum time to wait in milliseconds. Defaults to 10.
 * @returns {Uint8Array|null} A `Uint8Array` containing the received data, or `null` if the operation timed out with no data.
 */
channel.receive = function (handle_id, size, timeout) {
	if (handle_id === undefined || handle_id === null) {
		throw new Error("channel.receive: 'handle_id' parameter is required.")
	}
	if (size === undefined || size === null) {
		throw new Error("channel.receive: 'size' parameter is required.")
	}
	if (timeout === undefined || timeout === null) {
		timeout = 10
	}

	const res = nativeChannel.receive(handle_id, size, timeout)
	if (res === null) {
		return null
	}
	return new Uint8Array(res)
}

/**
 * Performs a special I/O control operation on a specified channel.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @param {number} request - The control command code, from `channel.IOC_SET_CMD` enum.
 * @param {*} arg - The argument for the request, which can be a number, string, or object.
 * @returns {boolean} True on success, false on failure.
 */
channel.ioctl = function (handle_id, request, arg) {
	if (handle_id === undefined || handle_id === null) {
		throw new Error("channel.ioctl: 'handle_id' parameter is required.")
	}
	// Validate that the provided request is a valid value from the enum
	if (!Object.values(channel.IOC_SET_CMD).includes(request)) {
		throw new Error(`channel.ioctl: invalid 'request' parameter. Please use a value from channel.IOC_SET_CMD.`);
	}
	return nativeChannel.ioctl(handle_id, request, arg)
}

/**
 * Closes a communication channel.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @returns {boolean} Always returns true on success.
 * @throws {Error} If the handle_id is invalid.
 */
channel.close = function (handle_id) {
	if (handle_id === undefined || handle_id === null) {
		throw new Error("channel.close: 'handle_id' parameter is required.")
	}
	return nativeChannel.close(handle_id)
}

/**
 * Flushes (clears) the input/output buffers of a specified channel.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @param {number} queue_selector - Which queue to flush. 0: Input, 1: Output, 2: Both.
 * @returns {boolean} True on success, false on failure.
 */
channel.flush = function (handle_id, queue_selector) {
	if (handle_id === undefined || handle_id === null) {
		throw new Error("channel.flush: 'handle_id' parameter is required.")
	}
	if (queue_selector === undefined || queue_selector === null) {
		throw new Error("channel.flush: 'queue_selector' parameter is required.")
	}
	return nativeChannel.flush(handle_id, queue_selector);
}

/**
 * Sets the communication parameters for a UART (serial) channel.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @param {number} baudrate - The baud rate, e.g., 9600, 115200.
 * @param {number} [databits=8] - The number of data bits.
 * @param {string} [parity='N'] - The parity: 'N' (None), 'O' (Odd), 'E' (Even).
 * @param {number} [stopbits=1] - The number of stop bits.
 * @returns {boolean} True on success, false on failure.
 */
channel.setUartParam = function (handle_id, baudrate, databits, parity, stopbits) {
	if (baudrate === undefined || baudrate === null) {
		throw new Error("channel.setUartParam: 'baudrate' parameter is required.");
	}
	// Set default values for optional parameters
	const final_databits = (databits === undefined || databits === null) ? 8 : databits;
	const final_parity = (parity === undefined || parity === null) ? 'N' : parity;
	const final_stopbits = (stopbits === undefined || stopbits === null) ? 1 : stopbits;

	const param_string = `${baudrate}-${final_databits}-${final_parity}-${final_stopbits}`;

	return channel.ioctl(handle_id, channel.IOC_SET_CMD.CHANNEL_IOC_SET_UART_PARAM, param_string);
}

/**
 * @typedef {object} WiegandGpioConfig
 * @property {number} [busy_time] - Busy time (in microseconds). Defaults to 50 in the C layer.
 * @property {number} [free_time] - Free time (in microseconds). Defaults to 100 in the C layer.
 * @property {number} [mode] - Working mode, see `channel.WIEGAND_MODE`. Defaults to 2 (34-bit) in the C layer.
 * @property {number} [log_level] - Log level. Defaults to 1 in the C layer.
 * @property {number} [wiegand_d0] - GPIO pin number for D0 data line. Defaults to 4 in the C layer.
 * @property {number} [wiegand_d1] - GPIO pin number for D1 data line. Defaults to 5 in the C layer.
 */

/**
 * Sets the GPIO pins and working parameters for a Wiegand channel.
 * @param {number} handle_id - The channel handle returned by `open()`.
 * @param {WiegandGpioConfig} config - Wiegand configuration object.
 * @returns {boolean} True on success, false on failure.
 */
channel.setWiegandGpio = function (handle_id, config) {
	if (config === undefined || config === null) {
		throw new Error("channel.setWiegandGpio: 'config' object is required.");
	}
	// The C layer provides default values, so passing a partial or empty object is safe.
	return channel.ioctl(handle_id, channel.IOC_SET_CMD.CHANNEL_IOC_SET_WIEGAND_GPIO, config);
}
export default channel;
